/*
* Copyright 2004-2010 mb_sx@hotmail.com
* All Rights Reserved.
*/
using System;
using System.Drawing;
using System.Drawing.Printing;
using System.IO;
using System.Windows.Forms;

namespace DeskNotes
{
	/// <summary>
	/// NotePrintDocument defines DeskNotes print. Handle single and multiple desknotes 
    /// printed from desktop and also from Archive. It uses RichTextBoxCtrl to render RTF properly.
	/// </summary>
	public class NotePrintDocument : PrintDocument
	{
		const int BOTTOM_PAGE = 5;
					
		private IPrintableNote[] notes;
		private Font defaultPrintFont = new Font("Courier New", 9);
        private Font footerFont = new Font("Arial", 9);
        const int RESERVE = 4;
        private int footerHeight = 0;
		private int noteID;
		private int pageID;
		//private string textForNextPage = "";
		static private int ticks = System.Environment.TickCount;
		private StringFormat stringFormat = new StringFormat(StringFormat.GenericTypographic);
        private RichTextBoxPrintCtrl rtfPrint;
        int checkPrint;
        bool hasMorePages;
        int printTime;
		
		internal Font DefaultFont
		{
			set 
			{
				defaultPrintFont = value;
			}
		}


		internal NotePrintDocument(PageSettings pageSettings, IPrintableNote[] notes)
		{
			//this.DefaultPageSettings = pageSettings;
            this.DefaultPageSettings.Margins = pageSettings.Margins;
			this.notes = notes;
		}

		static private void PrintTicks(string text)
		{
			int tick = ticks;
			ticks = System.Environment.TickCount;
		}

        internal NotePrintDocument( PageSettings pageSettings, IPrintableNote note)
        {

            //this.DefaultPageSettings = pageSettings;
            this.DefaultPageSettings.Margins = pageSettings.Margins;
            this.notes = new IPrintableNote[1];
            this.notes[0] = note;
            
        }

        /// <summary>
        /// Clean up any resources being used.
        /// </summary>
        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (rtfPrint != null)
                {
                   rtfPrint.Dispose();
                }
            }
            base.Dispose(disposing);
        }

		protected override void OnBeginPrint(PrintEventArgs e) 
		{
            
            printTime = Environment.TickCount; 
            /*if (!IsInsidePrintableArea())
            {
                start = Environment.TickCount - start;
                Logger.WriteLine("Miliseconds in IsInsidePrintableArea: {0}", start);

                if (DialogResult.No == MessageBox.Show(MainResource.GetString("PRINT_OUT_PRINTABLE"), MainResource.GetString("APP_TITLE"), MessageBoxButtons.YesNo, MessageBoxIcon.Exclamation))
                {
                    e.Cancel = true;
                }
            }*/

            DocumentName = MainResource.GetString("APP_TITLE");
            base.OnBeginPrint(e);
            // Save system resources for RichTextBox. Create it only when print is not cancelled.
            if (!e.Cancel)
            {
                noteID = 0;
                pageID = 1;
                checkPrint = 0;
                rtfPrint = new RichTextBoxPrintCtrl();
                hasMorePages = false;
            }
		}

        /// <summary>
        /// Verifies that margins are inside PrintableArea of a page. Not used as getting PrintableArea & PaperSize takes 2 seconds
        /// </summary>
        /// <returns>true if margins are inside the page printable area, otherwise false.</returns>
        private bool IsInsidePrintableArea()
        {
            int start = Environment.TickCount;
            RectangleF pArea = this.DefaultPageSettings.PrintableArea;
            start = Environment.TickCount - start;
            Logger.WriteLine("Milis to PrintableArea: {0}", start);
            
            start = Environment.TickCount;
            Margins margins = this.DefaultPageSettings.Margins;
            start = Environment.TickCount - start;
            Logger.WriteLine("Milis to Margins: {0}", start);
            
            start = Environment.TickCount;
            PaperSize pSize = this.DefaultPageSettings.PaperSize;
            start = Environment.TickCount - start;
            Logger.WriteLine("Milis to PaperSize: {0}", start);

            int height = pSize.Height - (margins.Top + margins.Bottom);
            int width = pSize.Width - (margins.Left + margins.Right);
            if (height > pArea.Height)
                return false;
            if (width > pArea.Width)
                return false;
            if (margins.Left < pArea.Left)
                return false;
            if ((margins.Left + width) > pArea.Right)
                return false;
            if (margins.Top < pArea.Top)
                return false;
            if ((margins.Top + height) > pArea.Bottom)
                return false;
            return true;
        }

		protected override void OnEndPrint(PrintEventArgs e)
		{
			base.OnEndPrint(e);
            rtfPrint.Dispose();
            rtfPrint = null;

            printTime = Environment.TickCount - printTime;
            Logger.WriteLine("===Milis spent printing: {0}===", printTime);
		}

        protected override void OnPrintPage(PrintPageEventArgs e)
        {
            int start = Environment.TickCount;
            
            int topOfPrint = 0;
            base.OnPrintPage(e);
            
            // initialization code - margins => must work with RichTextBoxPrintCtrl
            Graphics gdiPage = e.Graphics;
            // get footer height by which is then page make shorter
            footerHeight = (int)footerFont.GetHeight(gdiPage) + RESERVE;            
            
            // if printing more desknotes then init pen for line which divides them
            Pen linePen = new Pen(Color.Black, 1);
            
            topOfPrint = e.MarginBounds.Top;

            // Previous PagePrint did not print whole page continue where if finished
            if (hasMorePages)
            {
                hasMorePages = false;
                // print note using RichTextBoxPrintCtrl
                checkPrint = rtfPrint.Print(checkPrint, rtfPrint.TextLength, e, ref topOfPrint, footerHeight);
                // Check for more pages
                if (checkPrint < rtfPrint.TextLength)
                    e.HasMorePages = true;
                else
                {   // Note is completely printed
                    noteID++;
                    // if more notes processed then print line here
                    if (notes.Length > 1)
                    {
                        gdiPage.DrawLine(linePen, e.MarginBounds.Left, topOfPrint, e.MarginBounds.Right, topOfPrint);
                        topOfPrint += 1;
                    }
                }
            }

            // normal print which does not continue from previous page
            while ((!e.HasMorePages) && (noteID < notes.Length))
			{
                // if printing from Archive then print Category Header name here. Use RtfPrintCtrl
                if (notes[noteID].HasHeader)
                {
                    rtfPrint.Font = defaultPrintFont;
                    rtfPrint.Text = notes[noteID].HeaderText;
                    checkPrint = rtfPrint.Print(0, rtfPrint.TextLength, e, ref topOfPrint, footerHeight);
                    if (checkPrint < rtfPrint.TextLength)
                    {
                        e.HasMorePages = true;
                        break;
                    }
                    else
                        e.HasMorePages = false;
                }
                
                // set text to RTFPrint
                rtfPrint.Rtf = notes[noteID].BodyText;

                // print note using RichTextBoxPrintCtrl
                checkPrint = rtfPrint.Print(0, rtfPrint.TextLength, e, ref topOfPrint, footerHeight);
                // Check for more pages
                if (checkPrint < rtfPrint.TextLength)
                {
                    e.HasMorePages = true;
                    break;
                }
                else
                    e.HasMorePages = false;

                // if more notes processed then print line here
                if (notes.Length > 1)
                {   // if there is a space on page then move line 3 points below text to print the line
                    if (topOfPrint <= (e.MarginBounds.Bottom - (4 + footerHeight)))
                        topOfPrint += 3;
                    // Print line only if there is enough space on page for footer
                    if (topOfPrint <= (e.MarginBounds.Bottom - footerHeight))
                    {
                        // draw the line
                        gdiPage.DrawLine(linePen, e.MarginBounds.Left, topOfPrint, e.MarginBounds.Right, topOfPrint);
                        // move top of print by line width
                        topOfPrint += 1;
                        // if there is a space on page then move next text 3 points below the line. Prettier!
                        if (topOfPrint <= (e.MarginBounds.Bottom - (3 + footerHeight)))
                            topOfPrint += 3;
                    }
                }
                // remember last char on page or remember bottom position on a page

                // go to next note
                noteID++;
                
            }
            hasMorePages = e.HasMorePages;
            // Print page number
            PrintFooter(e, topOfPrint);
            start = Environment.TickCount - start;
            Logger.WriteLine("Milis in PrintPage(): {0}", start);
        }

        void PrintFooter( PrintPageEventArgs e, int topOfPrint)
        {

            // Print page number
            //float footerFontHeight = footerFont.GetHeight(e.Graphics);
            string pageInfo = string.Format(MainResource.GetString("PAGE"), pageID++);
            StringFormat footerFormat = new StringFormat(stringFormat);
            footerFormat.Alignment = StringAlignment.Center;
            int space = e.MarginBounds.Bottom - (topOfPrint + footerHeight);
            int upper;
            if (space >= 0)
            {
                upper = e.MarginBounds.Bottom - (footerHeight - RESERVE);
            }
            else
            {
                upper = topOfPrint + 1;
            }
            e.Graphics.DrawString(pageInfo, footerFont, Brushes.Black,
                new RectangleF(e.MarginBounds.Left, upper, e.MarginBounds.Width, footerHeight-RESERVE),
                footerFormat);
        }

       
		/*protected override void OnPrintPage(PrintPageEventArgs e)
		{
			base.OnPrintPage (e);
			//PrintTicks("OnPrintPage <start>: " );
			Graphics gdiPage = e.Graphics;
			float leftMargin = e.MarginBounds.Left;
			float topMargin = e.MarginBounds.Top;
			//float oneNoteLineHeight;
			float defaultPrintFontHeight = defaultPrintFont.GetHeight(gdiPage);
			float pointFromTop = topMargin;
			//int lines = 0;
			float headerLineHeight = 2; 
			SizeF printSize;
			
			stringFormat.FormatFlags =  StringFormatFlags.NoClip | StringFormatFlags.LineLimit | StringFormatFlags.MeasureTrailingSpaces;
		
			Pen linePen = new Pen(Color.Black, headerLineHeight);

			float pageHeight = e.MarginBounds.Height + topMargin;
			if (e.PageSettings.Margins.Bottom <= (defaultPrintFontHeight + BOTTOM_PAGE))
			{
				pageHeight -= defaultPrintFontHeight + BOTTOM_PAGE;
			}
			
			SizeF availableArea = new SizeF(e.MarginBounds.Width, pageHeight);

			while ((!e.HasMorePages) && (noteID < notes.Length))
			{	//get one note[noteID] line height
				Font f;
				if (null != notes[noteID].GetOwnFont)
					f = notes[noteID].GetOwnFont;
				else
					f = defaultPrintFont;

				//oneNoteLineHeight = f.GetHeight(gdiPage);

				if ("" == textForNextPage)
				{
					// measure notes[noteID] header height (line + header)
					availableArea.Height = pageHeight - (pointFromTop + headerLineHeight);
					string headerTextOver = "";
					//if header has to be printed then calculate its height
					if (notes[noteID].HasHeader)
					{
						string headerPrintText = notes[noteID].HeaderText;
						printSize = GetPrintedTextArea( ref headerPrintText, availableArea, e.Graphics, 
							defaultPrintFont, ref headerTextOver);
					} 
					//header has to be on same page as body
					if ("" != headerTextOver) 
					{
						e.HasMorePages = true;
						break;
					}
					// 
					// Print notes[noteID] header: line and header text (reminder or category, archive time
					// Print horizontal line
					gdiPage.DrawLine(linePen, leftMargin, pointFromTop, e.MarginBounds.Right, pointFromTop);
					pointFromTop += headerLineHeight;
					//Print header if it is set.
					if (notes[noteID].HasHeader)
					{
						availableArea.Height = pageHeight - pointFromTop;
						string headerPrintText = notes[noteID].HeaderText;
						printSize = GetPrintedTextArea( ref headerPrintText, availableArea, e.Graphics, 
							defaultPrintFont, ref headerTextOver);
						//gdiPage.DrawRectangle( new Pen(Color.Red, 1), leftMargin, pointFromTop, e.MarginBounds.Width, printSize.Height);

						StringFormat headerFormat = new StringFormat(stringFormat);
						headerFormat.Alignment = notes[noteID].HeaderAlign;
						
						gdiPage.DrawString( headerPrintText, defaultPrintFont, Brushes.Black, 
							new RectangleF(leftMargin, pointFromTop, e.MarginBounds.Width, printSize.Height),
							headerFormat); 
						pointFromTop += printSize.Height + 3; 
					}

					//
					// Printing of notes[noteID] text. This can be separated on a next page or split into more pages.
					//
					
					//compare complete desknote text height with remaining page height.
					// if page height is shorter then notes[noteID] needs to be divided.
					string printNow = notes[noteID].BodyText;
					availableArea.Height = pageHeight - pointFromTop;
					printSize = GetPrintedTextArea( ref printNow, availableArea, e.Graphics, 
						f, ref textForNextPage);
					if ("" != textForNextPage) 
						e.HasMorePages = true;
					else
						noteID++;
				
					//gdiPage.DrawRectangle( new Pen(Color.Blue, 1), leftMargin, pointFromTop, e.MarginBounds.Width, printSize.Height);
					// print text.
					gdiPage.DrawString(printNow, f, Brushes.Black, 
						new RectangleF(leftMargin, pointFromTop, e.MarginBounds.Width, printSize.Height),
						stringFormat); //StringFormat.GenericTypographic);
					pointFromTop += printSize.Height + 3;
				}
				else 
				{
					//////////////////////////////
					//
					// DeskNote's text continuous from previous page and will be printed
					// on this or more pages. DeskNote header is not printed in this case, 
					// it was printed on a previous page
					//
					//////////////////////////////
					string printNow = textForNextPage;
					textForNextPage = null;
					availableArea.Height = pageHeight - pointFromTop;
					printSize = GetPrintedTextArea( ref printNow, availableArea, e.Graphics, 
						f, ref textForNextPage);
					if ("" != textForNextPage)
						e.HasMorePages = true;
					else 
						noteID++;
					//gdiPage.DrawRectangle( new Pen(Color.Blue, 1), leftMargin, pointFromTop, e.MarginBounds.Width, printSize.Height);
					// print text for this page.
					gdiPage.DrawString(printNow, f, Brushes.Black, 
						new RectangleF(leftMargin, pointFromTop, e.MarginBounds.Width, printSize.Height),
						stringFormat); //StringFormat.GenericTypographic);
					pointFromTop += printSize.Height + 3;
				}
			}
			
			// 
			// Print page number at the bottom of the page
			//
			
			string pageInfo = string.Format(MainResource.GetString("PAGE"), pageID++);
			StringFormat footerFormat = new StringFormat(stringFormat);
			footerFormat.Alignment = StringAlignment.Center;
			gdiPage.DrawString( pageInfo, defaultPrintFont, Brushes.Black, 
				new RectangleF(leftMargin, pageHeight  + 2, e.MarginBounds.Width, defaultPrintFontHeight + 1),
				footerFormat);
			//PrintTicks( "PrintPage <return>: ");

		}
        

		/// <summary>
		/// Calculates area where text fits while printed compared to available area.
		/// </summary>
		/// <param name="text">Text to be printed into available area. Modified by method if area is smaller than text requires.</param>
		/// <param name="avalaibleArea">Avalable area on the page where text has to be printed</param>
		/// <param name="g">Graphis GDI</param>
		/// <param name="f">Font by which it is printed</param>
		/// <param name="textOver">Text which does not fit into available area and will be printed on a next page.</param>
		/// <returns>Size of the area where text fits in even it is shorter.</returns>
		internal SizeF GetPrintedTextArea(ref string text, SizeF availableArea, Graphics g, Font f, ref string textOver)
		{
			SizeF stringSize;
			int charsFitted, linesFilled;
			textOver = "";

			stringSize = g.MeasureString(text, f, availableArea, stringFormat, // StringFormat.GenericTypographic,
				out charsFitted, out linesFilled);
            // If charsFitted are zero then all text has to go over
			// If text does not fit there then cut it in first space or \n, \t before max chars that fits in.
			 if ((charsFitted < text.Length) && (0 < charsFitted))
			{
				textOver = text;
				string nL = " \n\t";
				char[] anyOf = nL.ToCharArray();
                // TODO - If three pages are printed then 2nd one is without page number and 3rd one has number 2.
				int spacePos = text.LastIndexOfAny( anyOf, charsFitted, charsFitted-1 );
				if (spacePos > 0)
				{
					text = text.Substring(0, spacePos);
					textOver = textOver.Substring(spacePos+1);
				} 
				else
				{
					text = text.Substring(0, charsFitted);
					textOver = textOver.Substring(charsFitted+1);
				}
            }
                 // nothing fits into remaining area on a page.
            else if (0 == charsFitted)
            {
                textOver = text;
                text = "";
            }
			// Sometimes last line is not printed. 
			// Adding 5% of font size helps in many cases
			stringSize.Height += f.GetHeight(g) / 20;
			
			return stringSize;
		}
         

        private void InitializeComponent()
        {
            // 
            // NotePrintDocument
            // 
            this.DocumentName = "DeskNotes document";
            this.OriginAtMargins = true;

        }
        */
		
	}
}
